#!/usr/bin/env perl

use strict;
use warnings;

# initialize memory
my @mem = ();
$mem[$_] = '00000000000000000000000000000000' foreach (0..384);


# read file and parse instructions
open SRC, '<', 'source.as';
{
  my $i = 0;
  $mem[$i++] = &parse($_) foreach (<SRC>);
}
close SRC;


# write mem file
open BIN, '>', 'mem.dat';
print BIN $_ . "\n" foreach (@mem);
close BIN;

exit 0;


# PARSER #

# %emitter contains the reference to the sub 
# to be called for the current instruction, 
# along with its opcode.
#
# NOTE: data opcode is fake.
sub parse
{
  my $instr = shift;

  return "00000000000000000000000000000000" if $instr =~ /^(?:nop)?$/;

  my %emitter = (
    'addi' => [ \&i_type, '010001' ],
    'subi' => [ \&i_type, '010010' ],
    'lw'   => [ \&i_type, '010011' ],
    'sw'   => [ \&i_type, '010100' ],
    'beqz' => [ \&i_type, '010101' ],
    'bnez' => [ \&i_type, '010110' ],
    'add'  => [ \&r_type, '001001' ],
    'sub'  => [ \&r_type, '001010' ],
    'slt'  => [ \&r_type, '001011' ],
    'sgt'  => [ \&r_type, '001100' ],
    'j'    => [ \&j_type, '100001' ],
    'data' => [ \&d_type, '000000' ],
  );

  # while checking, substitute opcode in instruction
  if ($instr =~ s/^(\w+)\s/$emitter{$1}->[1] /) {
    my $e = $emitter{$1}->[0] or die "ERROR: bad opcode";

    $e->(split /\s+/, $instr);
  }

}


# EMITTERS #

sub r_type
{
  my ($op, $rs, $rt, $rd) = @_;

  $rs =~ s/[rR]//;
  $rt =~ s/[rR]//;
  $rd =~ s/[rR]//;

  sprintf "%s%05b%05b%05b%011b", $op, $rs, $rt, $rd, 0;
}

sub i_type
{
  my ($op, $rs, $rt, $imm) = @_;

  $rs =~ s/[rR]//;
  $rt =~ s/[rR]//;

  sprintf "%s%05b%05b%016b", $op, $rs, $rt, hex $imm;
}

sub j_type
{
  my ($op, $addr) = @_;

  sprintf "%s%026b", $op, (hex $addr) >> 2;
}

sub d_type
{
  my ($op, $data) = @_;

  sprintf "%032b", hex $data;
}
